package top.lingkang.hibernate5.config;

import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.noear.solon.core.util.ResourceUtil;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.Properties;

/**
 * hibernate的配置入口，用于构建 SessionFactory
 *
 * @author lingkang
 * created by 2023/9/27
 * @since 1.0.0
 */
public class HibernateConfiguration extends Configuration {
    private static final CoreMessageLogger log = CoreLogging.messageLogger(HibernateConfiguration.class);
    private static boolean isInit = false;

    public HibernateConfiguration() {
        if (isInit)
            throw new IllegalArgumentException("hibernate已经初始化，不支持多数据源！");
        isInit = true;
    }

    /**
     * 添加实体包扫描，有hibernate的@Table、@Entity
     */
    public HibernateConfiguration addScanPackage(String... basePackage) {
        if (basePackage != null) {
            for (String pack : basePackage) {
                if (!pack.endsWith(".*"))
                    pack += ".*";
                Collection<Class<?>> classes = ResourceUtil.scanClasses(pack);
                for (Class<?> clazz : classes)
                    addAnnotatedClass(clazz);
            }
        }

        return this;
    }

    /**
     * 添加实体类，有hibernate的@Table、@Entity
     */
    public HibernateConfiguration addAnnotatedClass(Class<?>... entityClass) {
        if (entityClass != null)
            for (Class<?> entity : entityClass)
                addAnnotatedClass(entity);
        return this;
    }

    /**
     * 设置数据源，顶层可以是连接池
     */
    public HibernateConfiguration setDataSource(DataSource dataSource) {
        if (dataSource != null) {
            this.getProperties().put(AvailableSettings.DATASOURCE, dataSource);
        }
        return this;
    }

    /**
     * 设置 hibernate 的 Properties 配置
     * 可以参考 <a href="https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/cfg/AvailableSettings.html">
     * https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/cfg/AvailableSettings.html
     * </a>
     */
    public HibernateConfiguration setProperties(Properties properties) {
        if (properties != null) {
            properties.entrySet().forEach(obj -> {
                getProperties().put(obj.getKey(), obj.getValue());
            });
        }
        return this;
    }

    @Override
    public HibernateConfiguration addAnnotatedClass(Class annotatedClass) {
        super.addAnnotatedClass(annotatedClass);
        addAnnotatedClassName(annotatedClass);
        return this;
    }

    public HibernateConfiguration addAnnotatedClassName(Class<?> clazz) {
        return addAnnotatedClassName(clazz.getName());
    }

    public HibernateConfiguration addAnnotatedClassName(String className) {
        try {
            Field field = Configuration.class.getDeclaredField("metadataSources");
            field.setAccessible(true);
            MetadataSources metadataSources = (MetadataSources) field.get(this);
            metadataSources.addAnnotatedClassName(className);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return this;
    }

    /**
     * 构建 SessionFactory 工厂，创建 SessionFactory 的成本非常昂贵，因此，对于任何给定的数据库，
     * 应用程序应该只有一个关联的SessionFactory. 维护SessionFactoryHibernate 使用的所有服务，
     * Session(s)例如二级缓存、连接池、事务系统集成等。
     */
    @Override
    public SessionFactory buildSessionFactory() throws HibernateException {
        Object datasource = getProperties().get(AvailableSettings.DATASOURCE);
        if (datasource == null)
            throw new IllegalArgumentException("还未配置数据源：AvailableSettings.DATASOURCE: hibernate.connection.datasource");
        else if (!DataSource.class.isAssignableFrom(datasource.getClass())) {
            throw new IllegalArgumentException("hibernate.connection.datasource 不是数据源: " + datasource);
        }
        // 协调事务
        // getProperties().put(AvailableSettings.TRANSACTION_COORDINATOR_STRATEGY, new SolonTransactionCoordinatorBuilderImpl());
        getProperties().put(AvailableSettings.CURRENT_SESSION_CONTEXT_CLASS, "thread");
        if (!getProperties().contains(AvailableSettings.CONNECTION_PROVIDER))
            getProperties().put(AvailableSettings.CONNECTION_PROVIDER, new SolonDatasourceConnectionProviderImpl());
        return build();
    }

    private SessionFactory build() {
        log.debug("Building session factory using internal StandardServiceRegistryBuilder");
        StandardServiceRegistryBuilder registryBuilder = getStandardServiceRegistryBuilder();
        registryBuilder.applySettings(getProperties());
        StandardServiceRegistry serviceRegistry = registryBuilder.build();
        try {
            SessionFactory sessionFactory = buildSessionFactory(serviceRegistry);
            Object instance = Proxy.newProxyInstance(
                    this.getClass().getClassLoader(),
                    new Class<?>[]{SessionFactory.class},
                    new SessionFactoryInvocationHandler(sessionFactory)
            );
            return (SessionFactory) instance;
        } catch (Throwable t) {
            serviceRegistry.close();
            throw t;
        }
    }

    // --------------  一些基础设置 --------------------------------------

    /**
     * 打印SQL
     */
    public HibernateConfiguration setShowSql(boolean showSql) {
        getProperties().put(AvailableSettings.SHOW_SQL, showSql);
        return this;
    }

}
